home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 1998 October / Macworld (1998-10).dmg / Shareware World / Info / For Developers / MacZoop 1.8.4 / More Classes / Progress Dialog / ZProgress.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1998-07-08  |  17.2 KB  |  709 lines  |  [TEXT/CWIE]

  1. /*************************************************************************************************
  2. *
  3. *
  4. *            MacZoop - "the framework for the rest of us"         
  5. *
  6. *
  7. *
  8. *            ZProgress.cpp        -- a progress dialog
  9. *
  10. *
  11. *
  12. *
  13. *
  14. *            © 1996, Graham Cox
  15. *
  16. *
  17. *
  18. *
  19. *************************************************************************************************/
  20.  
  21. #include    "ZProgress.h"
  22. #include    "MacZoop.h"
  23. #include    "ProjectSettings.h"
  24.  
  25. #if APPEARANCE_MGR_AWARE
  26. #include    <Appearance.h>
  27. #endif
  28.  
  29. /*-----------------------------***  CONSTRUCTOR  ***------------------------------------*/
  30.  
  31. // this is an alternative constructor to set the striped/proportional mode explicitly, or
  32. // you can use the other constructor passing a negative value to default to striped mode.
  33.  
  34.  
  35. ZProgress::ZProgress(     ZCommander* aBoss,
  36.                           const short dialogID,
  37.                            const long maxValue,
  38.                            const short pType,
  39.                            const ProgType aMode )
  40.     : ZDialog( aBoss, dialogID )
  41. {
  42.     classID = CLASS_ZProgress;
  43.     theType = pType;
  44.     displayMode = -1;
  45.     
  46.     // how many InformProgress() calls between handling an event?
  47.     
  48.     evtProcRatio = 1;
  49.     evtProcCount = evtProcRatio;
  50.     macOS8Control = NULL;
  51.     
  52.     // max should not be less than 1 to avoid potential divide by zero error
  53.     
  54.     max = MAX( 1, ( maxValue & 0x7FFFFFFF ));
  55.     
  56.     value = 0;
  57.     deferTime = 0;
  58.     createTime = TickCount();
  59.     fAbort = FALSE;
  60.     SetRect( &pRect, 0, 0, 0, 0 );
  61.  
  62.     stripesPat = GetPixPat( kStdStripedPattern );
  63.     estTicksToFinish = -1;
  64.     
  65.     SetBeachBallCursor();
  66.     InitZWindow();
  67.  
  68.     // if maxValue is negative, default to indeterminate progress. Otherwise use whatever
  69.     // was passed along in the <aMode> parameter.
  70.     
  71.     if ( maxValue & 0x80000000 )
  72.         SetMode( kIndeterminateProgress );
  73.     else
  74.         SetMode( aMode );
  75. }
  76.  
  77.  
  78. ZProgress::ZProgress()
  79.     : ZDialog()
  80. {
  81.     classID = CLASS_ZProgress;
  82. }            
  83.  
  84.  
  85. /*------------------------------***  DESTRUCTOR  ***------------------------------------*/
  86.  
  87. ZProgress::~ZProgress()
  88. {
  89.     Hide();
  90.     // remove the stripes pattern if present
  91.     
  92.     if ( stripesPat )
  93.         DisposePixPat( stripesPat );
  94. }
  95.  
  96. /*---------------------------------***  SETUP  ***--------------------------------------*/
  97. /*    
  98.  
  99. overrides ZDialog to set up the button and bar according to the type
  100. ----------------------------------------------------------------------------------------*/
  101.  
  102. void        ZProgress::SetUp()
  103. {
  104.     // set up the text of the button to the requested type of dialog
  105.     
  106.     short    iType;
  107.     Handle    iHand;
  108.     Rect    iBox;
  109.  
  110.     if ( theType == kNoButton )
  111.     {
  112.         HideItem( kCancelButton );
  113.         
  114.         // extend the length of the bar in this case so that it is properly
  115.         // centred in the dialog window. (Looks better).
  116.         
  117.         GetDialogItem( macWindow, kBarItem, &iType, &iHand, &iBox );
  118.         
  119.         iBox.right = macWindow->portRect.right - iBox.left;
  120.         SetDialogItem( macWindow, kBarItem, iType, iHand, &iBox );
  121.     }
  122.     else
  123.     {
  124.         // the button is visible, but maybe its text is different.
  125.         // By default it is Cancel, but may need to say "Stop"
  126.         
  127.         if ( theType == kStopType )
  128.         {
  129.             Str31    stopStr;
  130.             
  131.             GetDialogItem(macWindow, kCancelButton, &iType, &iHand, &iBox);
  132.             
  133.             // sanity check- this IS a control, isn't it?
  134.             
  135.             if ( iType & ctrlItem )
  136.             {
  137.                 GetIndString( stopStr, 128, kStopStringID );
  138.                 SetControlTitle(( ControlHandle ) iHand, stopStr );
  139.             }
  140.         }
  141.     }
  142.     
  143.     // if we have the appearance mgr, make a control for doing the system progress bar
  144.     
  145.     #if APPEARANCE_MGR_AWARE
  146.     if ( gMacInfo.hasAppearanceMgr )
  147.     {
  148.         GetDialogItem( macWindow, kBarItem, &iType, &iHand, &iBox );
  149.         OffsetRect( &iBox, 0, -1 );
  150.         InsetRect( &iBox, -1, 0 );
  151.  
  152.         macOS8Control = NewControl( macWindow,
  153.                                     &iBox,
  154.                                     NULL,
  155.                                     TRUE,
  156.                                     0,
  157.                                     0,
  158.                                     iBox.right - iBox.left,
  159.                                     kControlProgressBarProc,
  160.                                     (long) this );
  161.     }
  162.     #endif
  163.     // call inherited SetUp in case base class wants to do something
  164.  
  165.     ZDialog::SetUp();
  166. }
  167.  
  168. /*---------------------------------***  DRAW  ***---------------------------------------*/
  169. /*    
  170. refresh the dialog
  171. ----------------------------------------------------------------------------------------*/
  172.  
  173. void        ZProgress::Draw()
  174. {
  175.     SetRect( &pRect, 0, 0, 0, 0 );
  176.     ZDialog::Draw();
  177. }
  178.  
  179.  
  180. /*-----------------------------***  DRAWUSERITEM  ***-----------------------------------*/
  181. /*    
  182.  
  183. Draws the progress bar user item
  184. ----------------------------------------------------------------------------------------*/
  185.  
  186. void        ZProgress::DrawUserItem( const short item )
  187. {
  188.     // override to draw the progress bar user-item. This is also called directly when
  189.     // InformProgress is called to update the bar.
  190.     
  191.     if ( item == kBarItem )
  192.     {
  193.         short            iType, barLength;
  194.         Handle            iHand;
  195.         Rect            iBox, barBox;
  196.         PixPatHandle    backPP, forePP;
  197.         
  198.         GetDialogItem( macWindow, item, &iType, &iHand, &iBox );
  199.         
  200.         if ( !macOS8Control )
  201.         {
  202.             FrameRect( &iBox );
  203.                         
  204.         #ifdef _GREYSCALE_APPEARANCE
  205.  
  206.             FrameGrayRect( &iBox );
  207.         
  208.         #endif
  209.         }
  210.         // if we have the appearance manager, we use the control DEF that implements the
  211.         // current appearance to draw the bar
  212.         
  213.         InsetRect( &iBox, 1, 1 );
  214.  
  215.         // if this is a striped bar, all we do is cycle the stripe and
  216.         // repaint it.
  217.         
  218.         if ( displayMode == kIndeterminateProgress )
  219.         {
  220.             #if APPEARANCE_MGR_AWARE
  221.  
  222.             if ( macOS8Control )
  223.             {
  224.                 mode = TRUE;
  225.                 
  226.                 SetControlData( macOS8Control,
  227.                                 kControlNoPart,
  228.                                 kControlProgressBarIndeterminateTag,
  229.                                 sizeof( Boolean ),
  230.                                 (Ptr) &mode );
  231.                 IdleControls( macWindow );
  232.             }
  233.             else
  234.             {
  235.             #endif    
  236.                 
  237.                 if ( stripesPat )
  238.                 {
  239.                     CycleStripe();
  240.                     FillCRect( &iBox, stripesPat );
  241.                 }
  242.                 else
  243.                     InvertRect( &iBox );    // not a great deal we can do if no resources...
  244.                     
  245.             #if APPEARANCE_MGR_AWARE
  246.             }
  247.             #endif
  248.         }
  249.         else
  250.         {
  251.             // compute the size of the barBox, based on max and value
  252.             
  253.             barLength = (short)(((double) value / (double) max) * (long)( iBox.right - iBox.left ));            
  254.             barBox = iBox;
  255.             barBox.right = MIN( barBox.left + barLength, iBox.right );
  256.             
  257.             // if this is different to the previously calculated bar, repaint it
  258.             
  259.             if (! EqualRect( &pRect, &barBox ))
  260.             {
  261.                 #if APPEARANCE_MGR_AWARE
  262.  
  263.                 if ( macOS8Control )
  264.                 {
  265.                     mode = FALSE;
  266.                     
  267.                     SetControlData( macOS8Control,
  268.                                     kControlNoPart,
  269.                                     kControlProgressBarIndeterminateTag,
  270.                                     sizeof( Boolean ),
  271.                                     (Ptr) &mode );
  272.                     
  273.                     SetControlValue( macOS8Control, barLength );
  274.                 }
  275.                 else
  276.                 {
  277.                 #endif    
  278.                     
  279.                     try
  280.                     {
  281.                         // if this is the first update, pRect will be empty, so
  282.                         // simply fill in the background
  283.                         
  284.                         if ( EmptyRect( &pRect ))
  285.                         {
  286.                             FailNIL( backPP = GetPixPat( kStdBackPattern ));
  287.                             FillCRect( &iBox, backPP );
  288.                             DisposePixPat( backPP );
  289.                         }
  290.                         
  291.                         FailNIL( forePP = GetPixPat( kStdBarPattern ));
  292.                         FillCRect( &barBox, forePP );
  293.                         DisposePixPat( forePP );
  294.                     }
  295.                     catch ( OSErr err )
  296.                     {
  297.                         // if the patterns don't exist, paint the bar instead.
  298.                         
  299.                         PaintRect( &barBox );
  300.                         
  301.                         // do not propagate exceptions from here
  302.                     }
  303.                     
  304.                 #if APPEARANCE_MGR_AWARE
  305.                 }
  306.                 #endif
  307.                 // update pRect if the window is visible.
  308.                 
  309.                 if ( IsVisible())
  310.                     pRect = barBox;
  311.             }
  312.         }
  313.     }
  314.     else
  315.         ZDialog::DrawUserItem( item );
  316. }
  317.  
  318.  
  319. /*-------------------------------***  CLICKITEM  ***------------------------------------*/
  320. /*    
  321. set the abort flag if the user clicked cancel or stop
  322. ----------------------------------------------------------------------------------------*/
  323.  
  324. void        ZProgress::ClickItem( const short theItem )
  325. {
  326.     fAbort = ( theItem == kCancelButton );
  327.     
  328.     #if _CANCEL_PROGRESS_THROWS_EXCEPTION
  329.     
  330.     if ( fAbort )
  331.         FailOSErr( userCanceledErr );
  332.         
  333.     #endif
  334. }
  335.  
  336.  
  337.  
  338.  
  339. /*-----------------------------***  INFORMPROGRESS  ***---------------------------------*/
  340. /*    
  341. called to keep the progress dialog moving as a lengthy operation proceeds
  342. ----------------------------------------------------------------------------------------*/
  343.  
  344. Boolean        ZProgress::InformProgress( const long progressSoFar )
  345. {
  346.     CGrafPtr    savePort;
  347.     GDHandle    saveDevice;
  348.     
  349.     // save the current port and device in case we were called from
  350.     // some function that had changed it. Note that the caller should be
  351.     // aware that the port, device, etc MAY not be exactly preserved
  352.     // across calls to this, since this processes events, etc.
  353.     
  354.     GetGWorld( &savePort, &saveDevice );
  355.     SetGDevice( GetMainDevice());
  356.     
  357.     // update the value we have got to here
  358.     
  359.     value = progressSoFar;
  360.     
  361.     // if it is time to show the progress dialog, make it visible
  362.     
  363.     if (( TickCount() > ( createTime + deferTime )) && !IsVisible())
  364.     {
  365.         Select();
  366.         PerformUpdate();
  367.         SetBusyArrowCursor();
  368.     }
  369.         
  370.     // let the application handle any pending event, so we get updated, etc. etc
  371.     // any exceptions arising are handled by the caller. Note that in the
  372.     // event of an exception, the device is set to the main device, which
  373.     // is possibly safer, though if the caller knows different, it should
  374.     // try to do the right thing.
  375.     
  376.     // N.B. This is now "geared" down to prevent the progress-monitored function from going
  377.     // too slowly. This processes one event for every <n> calls of this method, where <n> is
  378.     // set by <evtProcRatio>. This defaults to 1, but you can change it to anything you want
  379.     // from 1->32768.
  380.     
  381.     evtProcCount--;
  382.     
  383.     if ( evtProcCount <= 0 )
  384.     {
  385.         gApplication->Process1Event();
  386.         evtProcCount = evtProcRatio;
  387.     }
  388.     
  389.     // redraw the progress bar & time remaining info
  390.     
  391.     Focus();
  392.     DrawUserItem( kBarItem );
  393.     EstimateCompletionTime();
  394.     UpdateTimeToComplete();
  395.     
  396.     // restore what we think is the port and device
  397.     
  398.     SetGWorld( savePort, saveDevice );
  399.  
  400.     return !fAbort;    // carry on until this flag is set
  401. }
  402.  
  403.  
  404. /*--------------------------------***  SETDELAY  ***------------------------------------*/
  405. /*    
  406. set the initial deferment delay
  407. ----------------------------------------------------------------------------------------*/
  408.  
  409. void        ZProgress::SetDelay( const short delayTicks )
  410. {
  411.     deferTime = delayTicks;
  412. }
  413.  
  414.  
  415. /*-------------------------------***  SETMESSAGE  ***-----------------------------------*/
  416. /*    
  417. set the message string to the string passed
  418. ----------------------------------------------------------------------------------------*/
  419.  
  420. void        ZProgress::SetMessage( const Str255& aMsg )
  421. {
  422.     SetValue( kMessageItem, aMsg );
  423. }
  424.  
  425.  
  426. /*-------------------------------***  SETMESSAGE  ***-----------------------------------*/
  427. /*    
  428. set the message to the STR# resource with ID and index passed
  429. ----------------------------------------------------------------------------------------*/
  430.  
  431. void        ZProgress::SetMessage( const short resID, const short index )
  432. {
  433.     Str255    aStr;
  434.     
  435.     GetIndString( aStr, resID, index );
  436.     SetMessage( aStr );
  437. }
  438.  
  439.  
  440. /*---------------------------------***  SETMODE  ***------------------------------------*/
  441. /*    
  442. switch from stripey to proportional and back
  443. ----------------------------------------------------------------------------------------*/
  444.  
  445. void        ZProgress::SetMode( ProgType aMode )                        
  446. {
  447.     if ( aMode != displayMode )
  448.     {
  449.         displayMode = aMode;
  450.         
  451.         if ( aMode == kIndeterminateProgress )
  452.         {
  453.             HideItem( kTimeRemLabel );
  454.             HideItem( kEstTimeDisplay );
  455.         }
  456.         else
  457.         {
  458.             ShowItem( kTimeRemLabel );
  459.             ShowItem( kEstTimeDisplay );
  460.         }    
  461.     
  462.         // force a re-draw of the whole bar
  463.         
  464.         Draw();
  465.     }
  466. }
  467.  
  468.  
  469. /*----------------------------------***  FILTER  ***------------------------------------*/
  470. /*    
  471. maps command-period to the cancel/stop button, unless a kNoButton type
  472. ----------------------------------------------------------------------------------------*/
  473.  
  474. Boolean        ZProgress::Filter( EventRecord* theEvent )
  475. {
  476.     Boolean fullyHandled = FALSE;
  477.  
  478.     if ( theType != kNoButton )
  479.     {
  480.         if ( theEvent->what == keyDown )
  481.         {
  482.             char theKey = theEvent->message & charCodeMask;
  483.             
  484.             if ((theKey == '.') &&
  485.                 ((theEvent->modifiers & cmdKey) == cmdKey))
  486.             {
  487.                 FakeClick( kCancelButton );
  488.                 
  489.                 fullyHandled = TRUE;
  490.             }
  491.         }
  492.     }
  493.     
  494.     return fullyHandled;
  495. }
  496.  
  497.  
  498. /*-------------------------------***  CYCLESTRIPE  ***----------------------------------*/
  499. /*    
  500. for stripe bar, this moves the pattern by <loop>, animating it. This is designed to be called
  501. repeatedly, and only changes the pattern every few (3, in this case) ticks.
  502. ----------------------------------------------------------------------------------------*/
  503.  
  504. void        ZProgress::CycleStripe()
  505. {
  506.     if ( stripesPat && IsVisible())
  507.     {
  508.         // see if it is time yet. Notethat this function re-uses the timing
  509.         // data members, but since they are only used before the dialog is visible,
  510.         // this is OK in this particular case.
  511.         
  512.         deferTime = TickCount();
  513.         
  514.         if ( deferTime > ( createTime + 3 ))
  515.         {
  516.             createTime = deferTime;
  517.     
  518.             // modify the pattern. We do this by copying the
  519.             // image data of the pattern to itself in another
  520.             // position, based on the pixmap dimensions.
  521.             
  522.             long    patImgSize;
  523.             short    rowBytes, loop = 2;
  524.             Ptr        tempBuffer;
  525.             
  526.             rowBytes = (*(*stripesPat)->patMap)->rowBytes & 0x3FFF;
  527.             patImgSize = GetHandleSize((*stripesPat)->patData );
  528.             
  529.             // copy the top row of the data into a temporary buffer
  530.             // that we create. This holds one row of image data.
  531.             
  532.             tempBuffer = NewPtr( rowBytes );
  533.             
  534.             do
  535.             {
  536.                 BlockMoveData(*(*stripesPat)->patData, tempBuffer, rowBytes);
  537.                 
  538.                 // copy the rest of the data up one row
  539.                 
  540.                 BlockMoveData((*(*stripesPat)->patData) + rowBytes, *(*stripesPat)->patData, patImgSize - rowBytes);
  541.             
  542.                 // copy the temp row to the bottom of the image
  543.                 
  544.                 BlockMoveData(tempBuffer, (*(*stripesPat)->patData) + patImgSize - rowBytes, rowBytes);
  545.             }
  546.             while (--loop);
  547.             
  548.             DisposePtr( tempBuffer );
  549.             PixPatChanged( stripesPat );
  550.         }
  551.     }
  552. }
  553.  
  554.  
  555.  
  556. /*--------------------------***  ESTIMATECOMPLETIONTIME  ***----------------------------*/
  557. /*
  558. estimates the time to complete based on the amount completed and the time taken so far.
  559. This updates <estTicksToFinish>, and does not directly display this information anywhere.
  560. n.b. if <estTicksToFinish> is -1, the time has not been calculated yet.
  561. ----------------------------------------------------------------------------------------*/
  562.  
  563. void        ZProgress::EstimateCompletionTime()
  564. {
  565.     // wait until at least 3% of the bar has been drawn, otherwise the time estimate
  566.     // is wildly exaggerated, due to insufficient sample data.
  567.     
  568.     if ((( value * 100 ) / max ) > 3 )
  569.     {
  570.         unsigned long    ticksTakenSoFar = TickCount() - createTime;
  571.         float            currentRate = (float) ticksTakenSoFar / (float) value;
  572.         long            newEst = (long)( currentRate * (float)( max - value ));
  573.         
  574.         // only increase the current estimate if it's greater by more than 20 seconds,
  575.         // since it may have been just down to a temporary pause...
  576.         
  577.         if ( estTicksToFinish == -1 || ( newEst < estTicksToFinish ) || newEst > ( estTicksToFinish + 1200 ))
  578.             estTicksToFinish = newEst;
  579.     }
  580. }
  581.  
  582.  
  583. /*---------------------------***  UPDATETIMETOCOMPLETE  ***-----------------------------*/
  584. /*
  585. sets up the "Time Remaining:" string. This checks <estTicksToFinish>, and builds a
  586. suitable string. This is called every time InformProgress is called, but only resets the
  587. string when it needs to, based on the time resolution indicated.
  588. ----------------------------------------------------------------------------------------*/
  589.  
  590. void        ZProgress::UpdateTimeToComplete()
  591. {
  592.     // the default behaviour is similar to the MacOS 8.x Finder. The string will be
  593.     // moderately vague in the interests of user friendliness "About a minute", etc.
  594.     
  595.     if ( estTicksToFinish == -1 )
  596.         return;
  597.     
  598.     static long    updateTicks = 0;
  599.     short        days, hours, minutes, seconds;
  600.     
  601.     days     =   estTicksToFinish / 5184000L;
  602.     hours     = ( estTicksToFinish - ( days * 5184000L )) / 216000L;
  603.     minutes = ( estTicksToFinish - ( days * 5184000L ) - ( hours * 216000L )) / 3600L;
  604.     seconds = ( estTicksToFinish - ( days * 5184000L ) - ( hours * 216000L ) - ( minutes * 3600L )) / 60L;
  605.     
  606.     // determine if it's time to update yet. We only update if it appears sensible to bother, based on
  607.     // the sort of time we're looking at, or once per minute, whichever is less.
  608.     
  609.     if ( TickCount() >= updateTicks )
  610.     {
  611.         // decide the form of the string, based on the time
  612.         
  613.         Str255    estStr;
  614.         Str15    xx, yy;
  615.         short    idx = 0;
  616.         
  617.         xx[0] = 0;
  618.         yy[0] = 0;
  619.         
  620.         if ( days )
  621.         {
  622.             // "more than 24 hours"
  623.             
  624.             idx = 1;
  625.             goto setEstTimeStr;
  626.         }
  627.         
  628.         if ( hours >= 8 )
  629.         {
  630.             // "more than x hours"
  631.         
  632.             NumToString( hours, xx );
  633.             idx = 2;
  634.             goto setEstTimeStr;
  635.         }
  636.         
  637.         if ( estTicksToFinish > 252000L  )        // > 1hr, 10 minutes
  638.         {
  639.             // "about x hours, y minutes"
  640.             
  641.             NumToString( hours, xx );
  642.             NumToString( minutes, yy );
  643.             idx = 3;
  644.             goto setEstTimeStr;
  645.         }
  646.         
  647.         if ( estTicksToFinish > 198000L )    // > 55 minutes
  648.         {
  649.             // "about an hour"
  650.             idx = 4;
  651.             goto setEstTimeStr;
  652.         }
  653.         
  654.         if ( estTicksToFinish > 144000L )    // > 40 minutes
  655.         {
  656.             // "less than an hour"
  657.             
  658.             idx = 5;
  659.             goto setEstTimeStr;
  660.          }
  661.          
  662.          if ( estTicksToFinish > 4200L )        // > 1 minute, 10 sec    
  663.          {
  664.              // "about x minutes, y seconds
  665.              
  666.             NumToString( minutes, xx );
  667.             NumToString( seconds, yy );
  668.             
  669.             if ( minutes == 1 )
  670.                 idx = 11;
  671.             else
  672.                 idx = 6;
  673.             goto setEstTimeStr;
  674.          }
  675.          
  676.          if ( estTicksToFinish > 3000L )        // > 50 seconds
  677.          {
  678.              // "about a minute"
  679.              
  680.              idx = 7;
  681.             goto setEstTimeStr;
  682.          }
  683.          
  684.          if ( estTicksToFinish > 1500L )        // > 25 seconds
  685.          {
  686.              // "less than a minute"
  687.              
  688.              idx = 8;    
  689.             goto setEstTimeStr;
  690.          }
  691.          
  692.          if ( estTicksToFinish > 120L )        // > 2 seconds
  693.          {
  694.              NumToString( seconds, xx );
  695.              idx = 9;
  696.              goto setEstTimeStr;
  697.          }
  698.          
  699.          idx = 10;
  700.     
  701.     setEstTimeStr:
  702.         GetIndString( estStr, kTimeEstimateStrID, idx );
  703.         ParamText( NULL, NULL, xx, yy );
  704.         SetValue( kEstTimeDisplay, estStr );
  705.         
  706.         updateTicks = TickCount() + 60;
  707.     }
  708. }
  709.